#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Datetime: 2020/11/30 9:49
# @Author  : thinkive_cfy_ide_3
# @Site    : 
# @File    : portfolio_holding_campisi_attribution.py
# @Software: PyCharm 

"""
债券组合campisi归因
"""

import numpy as np
import pandas as pd
from quant_researcher.quant.datasource_fetch.bond_api.bond_price_related import \
    get_bond_quote
from quant_researcher.quant.datasource_fetch.index_api.index_price_related import get_bond_index_quote
from quant_researcher.quant.project_tool import time_tool
from quant_researcher.quant.performance_attribution.fund_related.ret_decomposition \
    .asset_ret_decomposition import fund_asset_decomposition


def get_campisi_attribution(fund_code, start_date, end_date, holding_begin, holding_end, **kwargs):
    """

    :param start_date:期初持仓日期
    :param end_date:期末持仓日期
    :param pd.DataFrame holding_begin: 开始时点债券组合的详细权重信息，格式如下
    security_code：证券代码
    security_weight：证券权重
        ---------------+-----------------------+
        security_code | security_weight |
        ---------------+-----------------------+
            000333    |         0.0968        |
            000423    |         0.0192        |
            000568    |         0.0879        |
            000596    |         0.0816        |
            000651    |         0.0971        |
        ---------------+-----------------------+
    :param pd.DataFrame holding_end: 结束时点债券组合的详细权重信息，格式如下
    security_code：证券代码
    security_weight：证券权重
        ---------------+-----------------------+
        security_code | security_weight |
        ---------------+-----------------------+
            000333    |         0.0968        |
            000423    |         0.0192        |
            000568    |         0.0879        |
            000596    |         0.0816        |
            000651    |         0.0971        |
        ---------------+-----------------------+
    :return: 1. 个券的收益分解
               bond_code income_effect bond_ret treasurybond_effect credit_effect other_effect
               018007       0.003585  0.003542      0.003585    0.000572      0.000071
               019627       0.004019  0.002000      0.004019   -0.001328      0.000883
               170209       0.004199  0.003849      0.004199    0.000180      0.000383
               180203       0.004577  0.002575      0.004577   -0.000744      0.000638
               190304       0.003564 -0.023124      0.003564   -0.001557     -0.024890
               190307       0.004308 -0.013710      0.004308   -0.005872     -0.010974
               200304       0.004587  0.002297      0.004587   -0.001241      0.000772
    """
    conn_base = kwargs.pop('conn_base', None)
    conn_ty = kwargs.pop('conn_ty', None)
    conn_stock = kwargs.pop('conn_stock', None)

    # 获取组合的平均个债券的区间持仓权重
    holding_all = holding_begin.merge(holding_end, on=['security_code', 'bond_sname'], how='outer',
                                      suffixes=['_begin', '_end']).set_index('security_code')
    holding_all['security_weight'] = holding_all.fillna(0).mean(axis=1)

    # 由于债券持仓信息只是部分，为了保持最终债券分解的收益与大类资产收益率分解到债券的收益相等，因此需要对最终结果做调整
    result, decomp, attribu = fund_asset_decomposition(fund_code=fund_code, benchmark='TK30B70S',
                                                       start_date=start_date, end_date=end_date,
                                                       conn_base=conn_base, conn_ty=conn_ty, conn_stock=conn_stock)
    bond_return = decomp.loc[decomp['asset_type'] == 'bond', 'weighted_return'].iloc[-1]
    fund_total_ret = decomp['total_return'][0]

    # 获取组合的个债券区间估计全价，估计久期，到期收益率的数据
    bond_list = holding_all.index.unique().tolist()
    bond_df = get_bond_quote(bond_list, start_date.replace('-', ''), end_date.replace('-', ''),
                             select=['bond_code', 'end_date', 'eval_yield',
                                     'eval_aduration', 'deeval_fullprice'],
                             conn=conn_base)
    bond_df['end_date'] = bond_df['end_date'].apply(lambda x: x.replace('-', ''))
    bond_df['eval_yield'] /= 100
    bond_df = bond_df.drop_duplicates(['end_date', 'bond_code']).set_index(
        ['end_date', 'bond_code'])

    # 每个债券的区间收益
    bond_price = bond_df['deeval_fullprice'].unstack()
    each_bond_ret = bond_price.ffill().iloc[-1] / bond_price.bfill().iloc[0] - 1

    # 调整个券的权重使得 持有这些债券得到的收益率与大类收益率分解得到的收益率相等
    bond_holding_ret = (holding_all['security_weight'] * each_bond_ret).sum()  # 假设只持有这些债券的收益率
    if np.sign(bond_holding_ret) == np.sign(bond_return):  # 即持有这些债券得到的收益率与大类收益率分解得到的债券收益率是同向的
        # 则将每个债券的权重放大或缩小，是的持有这些债券得到的收益率与大类收益率分解得到的收益率相等
        holding_all['security_weight'] = holding_all['security_weight'] * (
                    bond_return / bond_holding_ret)
    elif (bond_return > 0 and bond_holding_ret < 0):
        # 个券区间收益率为正的债券代码
        bond_code_index_plus = each_bond_ret[each_bond_ret > 0].index
        bond_holding_ret_plus = (holding_all.loc[bond_code_index_plus, 'security_weight'] *
                                 each_bond_ret).sum()
        # 个券个券区间收益率为负的债券代码
        bond_code_index_minus = each_bond_ret[each_bond_ret < 0].index
        bond_holding_ret_minus = (holding_all.loc[bond_code_index_minus, 'security_weight'] *
                                  each_bond_ret).sum()

        coeff = (bond_return - bond_holding_ret) / (bond_holding_ret_plus)
        holding_all.loc[bond_code_index_plus, 'security_weight'] = holding_all.loc[
                                                                       bond_code_index_plus, 'security_weight'] * (
                                                                               1 + coeff)
        # holding_all.loc[bond_code_index_minus, 'security_weight'] = holding_all.loc[
        #                                                                 bond_code_index_minus, 'security_weight'] * (
        #                                                                         1 - coeff)


    # 收入效应
    ytm_un = bond_df['eval_yield'].unstack()
    period = time_tool.calc_date_diff(start_date, end_date, fmt_str="%Y-%m-%d") / 365
    income_effect = ytm_un.bfill().iloc[0] * period
    # 收益率变化
    delta_y = ytm_un.ffill().iloc[-1] - ytm_un.bfill().iloc[0]
    # 修正久期
    md_un = bond_df['eval_aduration'].unstack()
    md = md_un.bfill().iloc[0]
    # 国债
    treasury = get_bond_index_quote(index_code='CBA00661.CS', start_date=start_date, end_date=end_date,
                                    select=['index_code', 'trade_date', 'pjdqsyl_val'], conn=conn_base)
    treasury['pjdqsyl_val'] /= 100
    treasury = treasury.sort_values(by='trade_date')
    t_y_un = treasury.set_index(['trade_date', 'index_code'])['pjdqsyl_val'].unstack()
    delta_t_y = t_y_un.ffill().iloc[-1] - t_y_un.bfill().iloc[0]

    each_bond_decomp = pd.concat([income_effect.rename('income_effect'), delta_y.rename('delta_y'),
                                  md.rename('md'), each_bond_ret.rename('each_bond_ret'),
                                  holding_all['security_weight']],
                                 axis=1)
    each_bond_decomp['delta_t_y'] = delta_t_y.iloc[0]
    each_bond_decomp = each_bond_decomp.eval('treasurybond_effect = -md * delta_t_y')
    each_bond_decomp = each_bond_decomp.eval('credit_effect = -md * (delta_y - delta_t_y)')
    each_bond_decomp = each_bond_decomp.eval(
        'other_effect = each_bond_ret-treasurybond_effect-credit_effect-income_effect')
    each_bond_decomp = each_bond_decomp[
        ['income_effect', 'treasurybond_effect', 'credit_effect', 'other_effect',
         'each_bond_ret']].mul(each_bond_decomp['security_weight'], axis=0)

    portfolio_decomp = each_bond_decomp.sum()
    portfolio_decomp.index = ['all_income_effect', 'all_treasurybond_effect', 'all_credit_effect',
                              'all_other_effect', 'all_bond_ret']
    portfolio_decomp['fund_total_return'] = fund_total_ret

    each_bond_decomp['end_date'] = end_date
    each_bond_decomp['bond_sname'] = holding_all['bond_sname']

    return each_bond_decomp, portfolio_decomp


if __name__ == '__main__':
    pass
